package com.tegistuff.webos.run.debugger.infrastructure;

import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.tegistuff.webos.run.debugger.WebOSDebugProcess;
import com.tegistuff.webos.run.debugger.commands.WebOSLookUpCommand;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.*;

/**
 * WebOSDebuggerVarRepository.java
 *
 * This class stores and manages V8 objects and references.
 *
 * Copyright (C) 2011-2012 Timo Tegtmeier.
 * All rights reserved.
 */
public class WebOSDebuggerVarRepository {

    private WebOSDebugProcess myDebugProcess;
    private Map<Integer, JSONObject> myVars = new HashMap<Integer, JSONObject>();

    public WebOSDebuggerVarRepository(WebOSDebugProcess debugProcess) {
        myDebugProcess = debugProcess;
    }

    public final JSONObject resolve(JSONObject var) throws JSONException {
        if(var == null)
            throw new IllegalArgumentException("Variable may not be null");
        if(!var.has(WebOSDebuggerConstants.V8_REFERENCE))
            return var;
        
        int ref = var.getInt(WebOSDebuggerConstants.V8_REFERENCE);
        if(!myVars.containsKey(ref))
            loadObject(ref);

        JSONObject resolvedVar = myVars.get(ref);
        if(resolvedVar == null)
            throw new IllegalStateException("Unable to resolve variable");
        return resolvedVar;
    }

    public final JSONVarType getType(JSONObject var) {
        try {
            String v8type = var.getString(WebOSDebuggerConstants.V8_TYPE);
            if(WebOSDebuggerConstants.V8_TYPE_UNDEFINED.equals(v8type)) {
                return JSONVarType.Undefined;
            } else if(WebOSDebuggerConstants.V8_TYPE_NULL.equals(v8type)) {
                return JSONVarType.Null;
            } else if(WebOSDebuggerConstants.V8_TYPE_BOOL.equals(v8type)) {
                return JSONVarType.Boolean;
            } else if(WebOSDebuggerConstants.V8_TYPE_NUMBER.equals(v8type)) {
                Object o = var.get(WebOSDebuggerConstants.V8_VALUE);
                if(o instanceof Integer || o instanceof Long)
                    return JSONVarType.Int;
                return JSONVarType.Double;
            } else if(WebOSDebuggerConstants.V8_TYPE_STRING.equals(v8type)) {
                return JSONVarType.String;
            } else if(WebOSDebuggerConstants.V8_TYPE_OBJECT.equals(v8type)) {
                if(var.getString(WebOSDebuggerConstants.V8_CLASSNAME).equals(WebOSDebuggerConstants.V8_CLASSNAME_ARRAY))
                    return JSONVarType.Array;
                return JSONVarType.Object;
            } else if(WebOSDebuggerConstants.V8_TYPE_FUNCTION.equals(v8type)) {
                return JSONVarType.Function;
            }
        } catch (Exception e) {
        }
        return JSONVarType.Unknown;
    }
    
    public final String getAsString(JSONObject var, JSONVarType type) {
        try {
            switch(type) {
                case Null:
                case Undefined:
                    return "";
                case Boolean:
                    return Boolean.toString(var.getBoolean(WebOSDebuggerConstants.V8_VALUE));
                case String:
                    return "\"" + new String(var.getString(WebOSDebuggerConstants.V8_VALUE).getBytes(), "UTF-8") + "\"";
                case Int:
                    return Integer.toString(var.getInt(WebOSDebuggerConstants.V8_VALUE));
                case Double:
                    return Double.toString(var.getDouble(WebOSDebuggerConstants.V8_VALUE));
                case Object:
                    return String.format("%s : %s", var.getString(WebOSDebuggerConstants.V8_TEXT).substring(1),
                            var.getString(WebOSDebuggerConstants.V8_CLASSNAME));
                case Array:
                    return "Array";
                case Function:
                    return String.format("%s()", var.getString(WebOSDebuggerConstants.V8_INF_NAME));
            }
        } catch (Exception e) {
        }
        return WebOSDebuggerConstants.ERROR_VALUE;
    }

    public final void clear() {
        myVars.clear();
    }

    public final void append(JSONObject variables) throws JSONException {
        Iterator handles = variables.keys();
        while(handles.hasNext()) {
            String handle = (String)handles.next();
            myVars.put(Integer.parseInt(handle), variables.getJSONObject(handle));
        }
    }
    
    public final void append(JSONArray variables) throws JSONException {
        for(int i = 0; i < variables.length(); i++) {
            JSONObject var = variables.getJSONObject(i);
            int handle = var.getInt(WebOSDebuggerConstants.V8_HANDLE);
            myVars.put(handle, var);
        }
    }

    public final List<Integer> getMissingVariables() throws JSONException {
        List<Integer> result = new LinkedList<Integer>();  
        
        Iterator handles = myVars.keySet().iterator();
        while(handles.hasNext()) {
            JSONObject var = myVars.get(handles.next());
            if(var.has(WebOSDebuggerConstants.V8_PROPERTIES)) {
                JSONArray properties = var.getJSONArray(WebOSDebuggerConstants.V8_PROPERTIES);
                for(int i = 0; i < properties.length(); i++) {
                    int handle = properties.getJSONObject(i).getInt(WebOSDebuggerConstants.V8_REFERENCE);
                    if(!isAvailable(handle) && !result.contains(handle)) {
                        result.add(handle);
                        if(result.size() >= WebOSDebuggerConstants.MAX_VAR_PER_LOOKUP)
                            return result;
                    }
                }
            }
        }
        return result;
    }
    
    public final boolean isAvailable(Object handle) {
        int ref;
        if(handle instanceof Integer) {
            ref = (Integer)handle;
        } else {
            ref = Integer.parseInt(handle.toString());
        }
        return myVars.containsKey(ref);
    }
    
    private void loadObject(final int ref) {
        WebOSThreadHelper.runAndWait(new Runnable() {
            @Override
            public void run() {
                new Task.Modal(myDebugProcess.getSession().getProject(), "Looking up objects", false) {
                    @Override
                    public void run(ProgressIndicator indicator) {
                        indicator.setIndeterminate(true);
                        indicator.setText("Collecting unresolved objects");
                        indicator.setText2("Waiting for debugger...");
                        try {
                            List<Integer> missingVars = getMissingVariables();
                            if (!missingVars.contains(ref))
                                missingVars.add(ref);

                            WebOSLookUpCommand command = new WebOSLookUpCommand(missingVars);
                            try {
                                myDebugProcess.getDebuggerProxy().getCommandSpooler().runCommand(command).waitForCompletion();
                            } catch (InterruptedException e) {
                                myDebugProcess.reportInternalError(e);
                            }
                            append(command.getVariables());
                        } catch (JSONException ex) {
                            myDebugProcess.reportInternalError(ex);
                        }
                    }
                }.queue();
            }
        });
    }

    public enum JSONVarType {
        Null,
        Undefined,
        Boolean,
        Int,
        Double,
        String,
        Function,
        Object,
        Array,
        Unknown
    }
}
